/*********************************************************************
*            (c) 1995 - 2018 SEGGER Microcontroller GmbH             *
*                        The Embedded Experts                        *
*                           www.segger.com                           *
**********************************************************************
----------------------------------------------------------------------
File    : FlashPrg.c
Purpose : Implementation of RAMCode template
--------  END-OF-HEADER  ---------------------------------------------
*/
#include "FlashOS.h"
#include "gpio.h"
#include "usart.h"
#include "spi.h"
#include "sfud.h"
#include "xprintf.h"
/*********************************************************************
 *
 *       Defines (configurable)
 *
 **********************************************************************
 */

//
// Only compile in functions that make sense to keep RAMCode as small as possible
//
#define SUPPORT_ERASE_CHIP (1)           // To potentially speed up production programming: Erases whole flash bank / chip with special command
#define SUPPORT_NATIVE_VERIFY (1)        // Non-memory mapped flashes only. Flash cannot be read memory-mapped
#define SUPPORT_NATIVE_READ_FUNCTION (1) // Non-memory mapped flashes only. Flash cannot be read memory-mapped
#define SUPPORT_BLANK_CHECK (1)
#define SUPPORT_SEGGER_OPEN_ERASE (0)   // Flashes with uniform sectors only. Speed up erase because 1 OFL call may erase multiple sectors
#define SUPPORT_SEGGER_OPEN_PROGRAM (0) // Flashes with uniform sectors only. Speed up program because 1 OFL call may program multiple sectors
#define SUPPORT_SEGGER_OPEN_CalcCRC (0)
#define SUPPORT_TURBO_MODE (0) // Currently available for Cortex-M only

#define OFL_DEBUG_MODE 
#ifdef OFL_DEBUG_MODE
#define OFL_INFO(...)  xprintf(__VA_ARGS__)
#else
#define OFL_INFO(...)
#endif
/*********************************************************************
 *
 *       Defines (fixed)
 *
 **********************************************************************
 */
#define READ_BUF_SIZE 512

/*********************************************************************
 *
 *       Types
 *
 **********************************************************************
 */

/*********************************************************************
 *
 *       Static data
 *
 **********************************************************************
 */
//
// We use this dummy variable to make sure that the PrgData
// section is present in the output elf-file as this section
// is mandatory in current versions of the J-Link DLL
//
static volatile int _Dummy;

#if (SUPPORT_ERASE_CHIP == 0)
#define EraseChip NULL
#endif

#if (SUPPORT_NATIVE_VERIFY == 0)
#define Verify NULL
#endif

#if (SUPPORT_NATIVE_READ_FUNCTION == 0)
#define SEGGER_OPEN_Read NULL
#endif

#if (SUPPORT_BLANK_CHECK == 0)
#define BlankCheck NULL
#endif

#if (SUPPORT_SEGGER_OPEN_ERASE == 0)
#define SEGGER_OPEN_Erase NULL
#endif

#if (SUPPORT_SEGGER_OPEN_PROGRAM == 0)
#define SEGGER_OPEN_Program NULL
#endif

#if (SUPPORT_SEGGER_OPEN_CalcCRC == 0)
#define SEGGER_OPEN_CalcCRC NULL
#endif

#if (SUPPORT_TURBO_MODE == 0)
#define SEGGER_OPEN_Start NULL
#endif

/*********************************************************************
 *
 *       Static data
 *
 **********************************************************************
 */

/*********************************************************************
 *
 *       Public data
 *
 **********************************************************************
 */
// Mark start of <PrgData> segment. Non-static to make sure linker can keep this symbol. Dummy needed to make sure that
// <PrgData> section in resulting ELF file is present. Needed by open flash loader logic on PC side
volatile int PRGDATA_StartMarker __attribute__((section("PrgData")));

// Mark start of <PrgCode> segment. Non-static to make sure linker can keep this symbol.
const SEGGER_OFL_API SEGGER_OFL_Api __attribute__((section("PrgCode"))) = {
    NULL,
    Init,
    UnInit,
    EraseSector,
    ProgramPage,
    BlankCheck,
    EraseChip,
    Verify,
    SEGGER_OPEN_CalcCRC,
    SEGGER_OPEN_Read,
    SEGGER_OPEN_Program,
    SEGGER_OPEN_Erase,
    SEGGER_OPEN_Start,
};

#define PAGE_SIZE EXFLASH_SECTORS_SIZE
uint8_t read_buf[PAGE_SIZE] = {1};
uint32_t base_adr = EXFLASH_BASE_ADDR;
const sfud_flash *flash = NULL;
extern void SystemClock_Config(void);
extern void xputc_out(uint8_t ch);
/*********************************************************************
 *
 *       _FeedWatchdog
 *
 *  Function description
 *    Feeds the watchdog. Needs to be called during RAMCode execution in case of an watchdog is active.
 *    In case no handling is necessary, it could perform a dummy access, to make sure that this function is linked in
 */
static void _FeedWatchdog(void)
{
    *((volatile int *)&PRGDATA_StartMarker); // Dummy operation
}

/*********************************************************************
 *
 *       Init
 *
 *  Function description
 *    Handles the initialization of the flash module.
 *
 *  Parameters
 *    Addr: Flash base address
 *    Freq: Clock frequency in Hz
 *    Func: Specifies the action followed by Init() (e.g.: 1 - Erase, 2 - Program, 3 - Verify / Read)
 *
 *  Return value
 *    0 O.K.
 *    1 Error
 */
int Init(U32 Addr, U32 Freq, U32 Func)
{
    // 通用初始化
    if (flash == NULL)
    {
        SystemClock_Config();
        MX_GPIO_Init();
        MX_USART1_UART_Init();
        MX_SPI2_Init();

        xdev_out(xputc_out);

        if (sfud_init() == SFUD_SUCCESS)
        {
            flash = sfud_get_device_table();
            OFL_INFO("Init success.initAddr=0x%x,EXFLASH_BASE_ADDR=0x%x,EXFLASH_SECTORS_SIZE=0x%x \n", Addr,EXFLASH_BASE_ADDR, EXFLASH_SECTORS_SIZE);
        }
        else
        {
            OFL_INFO("Init failed.initAddr=0x%x,EXFLASH_BASE_ADDR=0x%x,EXFLASH_SECTORS_SIZE=0x%x \n", Addr,EXFLASH_BASE_ADDR, EXFLASH_SECTORS_SIZE);
            return 1;
        }
    }


    // 对各流程的专用初始化
    if (Func == 1)
    {
        // TODO
    }
    else if (Func == 2)
    {
        // TODO
    }
    else if (Func == 3)
    {
        // TODO
    }
    return 0;
}

/*********************************************************************
 *
 *       UnInit
 *
 *  Function description
 *    Handles the de-initialization of the flash module.
 *
 *  Parameters
 *    Func: Caller type (e.g.: 1 - Erase, 2 - Program, 3 - Verify)
 *
 *  Return value
 *    0 O.K.
 *    1 Error
 */
int UnInit(U32 Func)
{
    OFL_INFO("UnInit.fnc=%ld \n", Func);
    return (0);
}

/*********************************************************************
 *
 *       EraseSector
 *
 *  Function description
 *    Erases one flash sector.
 *
 *  Parameters
 *    SectorAddr: Absolute address of the sector to be erased
 *
 *  Return value
 *    0 O.K.
 *    1 Error
 */
int EraseSector(U32 SectorAddr)
{
    int result = 0;
    uint32_t block_start;
    block_start = SectorAddr - base_adr;
    result = sfud_erase(flash, block_start, flash->chip.erase_gran);
    if (result == SFUD_SUCCESS)
    {
        OFL_INFO("EraseSector success.addr=0x%x \n", block_start);
        return 0;
    }
    else
    {
        OFL_INFO("EraseSector failed.addr=0x%x \n", block_start);
        return result;
    }
}

/*********************************************************************
 *
 *       ProgramPage
 *
 *  Function description
 *    Programs one flash page.
 *
 *  Parameters
 *    DestAddr: Destination address
 *    NumBytes: Number of bytes to be programmed (always a multiple of program page size, defined in FlashDev.c)
 *    pSrcBuff: Point to the source buffer
 *
 *  Return value
 *    0 O.K.
 *    1 Error
 */
int ProgramPage(U32 DestAddr, U32 NumBytes, U8 *pSrcBuff)
{
    uint32_t start_addr = DestAddr - base_adr;

    if (sfud_write(flash, start_addr, NumBytes, pSrcBuff) == SFUD_SUCCESS)
    {
        OFL_INFO("ProgramPage success.DestAddr=0x%x,start_addr=0x%x,size=%ld \n", DestAddr, start_addr, NumBytes);
        return 0;
    }
    else
    {
        OFL_INFO("ProgramPage failed.addr=0x%x,size=%ld \n", DestAddr, NumBytes);
        return 1;
    }
}

/*********************************************************************
 *
 *       Verify
 *
 *  Function description
 *    Compares a specified number of bytes of a provided data
 *    buffer with the content of the device
 *
 *  Parameters
 *    Addr: Start address in memory which should be compared
 *    NumBytes: Number of bytes to be compared
 *    pBuff: Pointer to the data to be compared
 *
 *  Return value
 *    == (Addr + NumBytes): O.K.
 *    != (Addr + NumBytes): *not* O.K. (ideally the fail address is returned)
 *
 */
#if SUPPORT_NATIVE_VERIFY
U32 Verify(U32 Addr, U32 NumBytes, U8 *pBuff)
{

    uint32_t start_addr = Addr - base_adr;
    sfud_read(flash, start_addr, NumBytes, read_buf);

    for (int i = 0; i < PAGE_SIZE; i++)
    {
        if (read_buf[i] != pBuff[i])
        {
            OFL_INFO("Verify failed.adr=0x%lx \n", Addr + i);
            return (Addr + i); // Verification Failed (return address)
        }
    }

    OFL_INFO("MY Verify success.adr=0x%lx,sz=%ld \n", start_addr, NumBytes);
    return (Addr + NumBytes); // Done successfully
}
#endif

/*********************************************************************
 *
 *       BlankCheck
 *
 *  Function description
 *    Checks if a memory region is blank
 *
 *  Parameters
 *    Addr: Blank check start address
 *    NumBytes: Number of bytes to be checked
 *    BlankData: Pointer to the destination data
 *
 *  Return value
 *    0: O.K., blank
 *    1: O.K., *not* blank
 *    < 0: Error
 *
 */
#if SUPPORT_BLANK_CHECK
int BlankCheck(U32 Addr, U32 NumBytes, U8 BlankData)
{
    sfud_err result = SFUD_SUCCESS;
    uint32_t start_addr = Addr - base_adr;
    int remain_sz = 0;
    int one_read_sz = 0;

    if (NumBytes > INTPTR_MAX)
    {
        // BlankCheck一次检查最大为1G
        return 1;
    }
    else
    {
        remain_sz = NumBytes;
    }

    while (remain_sz > 0)
    {
        one_read_sz = remain_sz > PAGE_SIZE ? PAGE_SIZE : remain_sz;

        result = sfud_read(flash, start_addr, one_read_sz, read_buf);
        if (result != SFUD_SUCCESS)
        {
            OFL_INFO("BlankCheck error.adr=0x%x,sz=0x%lx,result=%d \n", start_addr, one_read_sz, result);
            return -1;
        }
        else
        {
            for (int i = 0; i < one_read_sz; i++)
            {
                if (read_buf[i] != BlankData)
                {
                    OFL_INFO("BlankCheck not blank.adr=0x%lx,sz=0x%lx \n", start_addr, i);
                    return (1); // BlankCheck Failed (return 1)
                }
            }
        }
        OFL_INFO("BlankCheck success.adr=%x,sz=0x%x BlankData=0x%x \n", start_addr, one_read_sz, BlankData);

        remain_sz = remain_sz - one_read_sz;
        start_addr = start_addr + one_read_sz;
    }

    return (0); // Memory space is blank
}
#endif

/*********************************************************************
 *
 *       EraseChip
 *
 *  Function description
 *    Erases the entire flash
 *
 *  Return value
 *    0: O.K.
 *    1: Error
 */
#if SUPPORT_ERASE_CHIP
int EraseChip()
{
    OFL_INFO("EraseChip.capacity=ox%x,write_mode=0x%x,erase_gran_cmd=0x%x,erase_gran=0x%x \n\r", flash->chip.capacity, flash->chip.write_mode, flash->chip.erase_gran_cmd, flash->chip.erase_gran);
    int result = 0;
    result = sfud_erase(flash, 0, flash->chip.capacity);
    if (result == SFUD_SUCCESS)
    {
        OFL_INFO("EraseChip success \r");
        return 0;
    }
    else
    {
        OFL_INFO("EraseChip failed.result=%d \r", result);
        return result; // Finished without Errors
    }
}
#endif

/*********************************************************************
 *
 *       SEGGER_OPEN_Read
 *
 *  Function description
 *    Reads a specified number of bytes into the provided buffer
 *
 *  Parameters
 *    Addr: Start read address
 *    NumBytes: Number of bytes to be read
 *    pBuff: Pointer to the destination data
 *
 *  Return value
 *    >= 0: O.K., NumBytes read
 *    <  0: Error
 *
 */
#if SUPPORT_NATIVE_READ_FUNCTION
int SEGGER_OPEN_Read(U32 Addr, U32 NumBytes, U8 *pDestBuff)
{
    sfud_err result = SFUD_SUCCESS;
    uint32_t start_addr = Addr - base_adr;

    result = sfud_read(flash, start_addr, NumBytes, pDestBuff);
    if (result != SFUD_SUCCESS)
    {
        OFL_INFO("SEGGER_OPEN_Read error.adr=0x%x,sz=0x%x,result=%d \n", start_addr, NumBytes, result);
        return -1;
    }
    else
    {
        OFL_INFO("SEGGER_OPEN_Read success.adr=0x%x,sz=0x%x \n", start_addr, NumBytes);
        return NumBytes;
    }

    return (NumBytes);
}
#endif

/*********************************************************************
 *
 *       SEGGER_OPEN_Program
 *
 *  Function description
 *    Programs a specified number of bytes into the target flash.
 *    NumBytes is either EXFLASH_PAGE_SIZE or a multiple of it.
 *
 *  Notes
 *    (1) This function can rely on that at least EXFLASH_PAGE_SIZE will be passed
 *    (2) This function must be able to handle multiple of EXFLASH_PAGE_SIZE
 *
 *  Parameters
 *    Addr: Start read address
 *    NumBytes: Number of bytes to be read
 *    pBuff: Pointer to the destination data
 *
 *  Return value
 *    0 O.K.
 *    1 Error
 *
 */
#if SUPPORT_SEGGER_OPEN_Program
int SEGGER_OPEN_Program(U32 DestAddr, U32 NumBytes, U8 *pSrcBuff)
{
    EnableMMAP(0);
    U32 NumPages;
    int r;

    NumPages = NumBytes / EXFLASH_PAGE_SIZE;
    r = 0;
    do
    {
        r = board_exFlash_WritePage(DestAddr - EXFLASH_BASE_ADDR, pSrcBuff, EXFLASH_PAGE_SIZE);
        if (r != 0)
        {
            EnableMMAP(1);
            return r;
        }
        DestAddr += EXFLASH_PAGE_SIZE;
        pSrcBuff += EXFLASH_PAGE_SIZE;
    } while (--NumPages);
    EnableMMAP(1);
    return 0;
}
#endif

/*********************************************************************
 *
 *       SEGGER_OPEN_Erase
 *
 *  Function description
 *    Erases one or more flash sectors
 *
 *  Notes
 *    (1) This function can rely on that at least one sector will be passed
 *    (2) This function must be able to handle multiple sectors at once
 *    (3) This function can rely on that only multiple sectors of the same sector
 *        size will be passed. (e.g. if the device has two sectors with different
 *        sizes, the DLL will call this function two times with NumSectors = 1)
 *
 *  Parameters
 *    SectorAddr: Address of the start sector to be erased
 *    SectorIndex: Index of the start sector to be erased
 *    NumSectors: Number of sectors to be erased. At least 1 sector is passed.
 *
 *  Return value
 *    0 O.K.
 *    1 Error
 *
 */
#if SUPPORT_SEGGER_OPEN_ERASE
int SEGGER_OPEN_Erase(U32 SectorAddr, U32 SectorIndex, U32 NumSectors)
{
    EnableMMAP(0);
    int ret = 0;
    uint32_t FlashAddr = SectorAddr - EXFLASH_BASE_ADDR;
    while (NumSectors)
    {
        if (NumSectors >= 16)
        {
            ret = !!board_exFlash_EraseBlock64K(FlashAddr);
            NumSectors -= 16;
            FlashAddr += 16 * 0x1000;
        }
        else if (NumSectors >= 8)
        {
            ret = !!board_exFlash_EraseBlock32K(FlashAddr);
            NumSectors -= 8;
            FlashAddr += 8 * 0x1000;
        }
        else
        {
            ret = !!board_exFlash_EraseSector(FlashAddr);
            NumSectors -= 1;
            FlashAddr += 0x1000;
        }
        if (ret)
            break;
    }
    EnableMMAP(1);
    return ret;
}
#endif

/*********************************************************************
 *
 *       SEGGER_OPEN_CalcCRC
 *
 *  Function description
 *    Calculates the CRC over a specified number of bytes
 *    Even more optimized version of Verify() as this avoids downloading the compare data into the RAMCode for
 * comparison. Heavily reduces traffic between J-Link software and target and therefore speeds up verification process
 * significantly.
 *
 *  Parameters
 *    CRC       CRC start value
 *    Addr      Address where to start calculating CRC from
 *    NumBytes  Number of bytes to calculate CRC on
 *    Polynom   Polynom to be used for CRC calculation
 *
 *  Return value
 *    CRC
 *
 *  Notes
 *    (1) This function is optional
 *    (2) Use "noinline" attribute to make sure that function is never inlined and label not accidentally removed by
 * linker from ELF file.
 */
#if SUPPORT_SEGGER_OPEN_CalcCRC
U32 SEGGER_OPEN_CalcCRC(U32 crc, U32 Addr, U32 NumBytes, U32 Polynom)
{
    OFL_INFO("SEGGER_OPEN_CalcCRC.crc=%d,adr=0x%x,sz=0x%x,Polynom=%d \n", crc, Addr, NumBytes, Polynom);
#if !SUPPORT_NATIVE_READ_FUNCTION
    uint8_t buf[READ_BUF_SIZE];
    uint32_t FlashAddr = Addr - EXFLASH_BASE_ADDR;

    while (NumBytes)
    {
        int op_len = NumBytes > READ_BUF_SIZE ? READ_BUF_SIZE : NumBytes;
        int ret = board_exFlash_Read(FlashAddr, buf, op_len);
        if (ret != op_len)
            return ret;
        crc = SEGGER_OFL_Lib_CalcCRC(&SEGGER_OFL_Api, crc, (uint32_t)&FlashAddr, op_len, Polynom);
    }
    return crc;
#else
    // Use lib function from SEGGER by default. Pass API pointer to it because it
    // may need to call the read function (non-memory mapped flashes)
    crc = SEGGER_OFL_Lib_CalcCRC(&SEGGER_OFL_Api, crc, Addr, NumBytes, Polynom);
    return crc;
#endif
}
#endif

/*********************************************************************
 *
 *       SEGGER_OPEN_Start
 *
 *  Function description
 *    Starts the turbo mode of flash algo.
 *    Currently only available for Cortex-M based targets.
 */
#if SUPPORT_TURBO_MODE
void SEGGER_OPEN_Start(volatile struct SEGGER_OPEN_CMD_INFO *pInfo)
{
    SEGGER_OFL_Lib_StartTurbo(&SEGGER_OFL_Api, pInfo);
}
#endif

/**************************** End of file ***************************/
